package com.gjy.mongo;

import com.mongodb.*;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.management.JMXConnectionPoolListener;

import javax.net.ssl.SSLContext;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 单节点:   MongoClients.create("mongodb://101.42.141.104:27017");
 * 副本集:   MongoClients.create("mongodb://101.42.141.104:27017,101.42.141.104:27018");
 * 指定副本集的一个或多个节点后，MongoClient会自动找到当前副本集中的主节点和备节点。
 * 分片集群: MongoClients.create("mongodb://101.42.141.104:27017,101.42.141.104:27018");
 * 连接分片集群与连接副本集的方式类似，但所提供的节点是mongos，而不是分片上的数据节点。
 *
 * @author 宫静雨
 * @version 1.0
 * @since 2023-01-30 08:57:13
 */
public class MongoUtil {

    private static final String IP = "101.42.141.104";
    private static final Integer PORT = 27017;
    private static final String DB = "test";
    private static final String USERNAME = "";
    private static final String PASSWORD = "";

    public static MongoDatabase connect() {
        MongoClient client = MongoClients.create("mongodb://101.42.141.104:27017");
        return client.getDatabase(DB);
    }

    /**
     * 使用构建器构建
     * （1）MongoDB服务器一般都会开启鉴权，如何设定对应的用户名、密码信息？
     * （2）作为数据库的客户端，如果频繁地建立新的连接，将会产生大量的资源开销，如何使用连接池并做出合理的设置？
     * （3）怎么定义数据库访问的超时时间？
     * （4）在开放式的网络环境，比如在公有云中，为了保证数据的传输安全，如何为客户端启用SSL？
     */
    public static MongoDatabase build() {
        // 服务器实例
        List<ServerAddress> servers = new ArrayList<>();
        servers.add(new ServerAddress(IP, PORT));
        // 配置构建器
        MongoClientSettings clientSettings = MongoClientSettings.builder()
                .applyToClusterSettings(builder -> builder.hosts(servers))
                .addCommandListener(new SlowLogListener(0L))// 监听慢操作日志
                .applyToConnectionPoolSettings(builder -> {
                    builder.addConnectionPoolListener(new JMXConnectionPoolListener());
                })
                .build();
        // 构建 Client 实例
        MongoClient client = MongoClients.create(clientSettings);
        PoolBeanMonitor monitor = new PoolBeanMonitor();
        monitor.start();
        // 当程序停止时，调用 PoolBeanMonitor 的 stop 方法，停止定时任务
        Runtime.getRuntime().addShutdownHook(new Thread(monitor::stop));
        return client.getDatabase(DB);
    }

    public static MongoDatabase more() throws NoSuchAlgorithmException {
        // 设置用户名/密码 SCRAM-SHA-256
        MongoCredential credential = MongoCredential.createCredential(USERNAME, DB, PASSWORD.toCharArray());
        // 使用TLS/SSL 该功能要求先为MongoDB服务器启用TLS/SSL支持，从MongoDB 4.0版本开始，需使用TLS 1.1及以上的版本。
        SSLContext sslContext = SSLContext.getDefault();

        MongoClientSettings build = MongoClientSettings.builder()
                .credential(credential)
                .applyToSslSettings(builder -> {
                    builder.enabled(true);
                    builder.context(sslContext);
                    builder.invalidHostNameAllowed(true);
                })
                .applyToConnectionPoolSettings(builder -> {//连接池配置
                    builder.minSize(10);//最小连接数
                    builder.maxSize(100);//最大连接数
                    builder.maxConnectionIdleTime(1, TimeUnit.MINUTES);//连接最大空闲时间
                    builder.maxConnectionIdleTime(10, TimeUnit.MINUTES);//连接最大生命周期
                })
                .applyToSocketSettings(builder -> {
                    builder.connectTimeout(10, TimeUnit.SECONDS);//TCP建立连接超时
                    builder.readTimeout(30, TimeUnit.SECONDS);//TCP Socket 读取超时
                })
                .retryWrites(true)//写重试
                .retryReads(true)//读重试
                .readPreference(ReadPreference.secondaryPreferred())// 优先读备节点
                .writeConcern(WriteConcern.ACKNOWLEDGED)//写关注=ACK
                .build();
        MongoClient client = MongoClients.create(build);
        return client.getDatabase(DB);
    }
}
